const express = require('express')
const ExcelJS = require('exceljs')
const PDFDocument = require('pdfkit')
const dayjs = require('dayjs')

const { AuditLogModel, AuditRuleModel } = require('../model/auditLog')
const { handleHighRiskAlert } = require('../services/auditAlertService')

const router = express.Router()

const success = (res, data = null, message = 'OK') =>
  res.send({
    code: 200,
    success: true,
    message,
    data,
  })

const failure = (res, message = '请求失败', code = 500, data = null) =>
  res.status(code).send({
    code,
    success: false,
    message,
    data,
  })

const riskPriority = {
  low: 1,
  medium: 2,
  high: 3,
}

const applyRoleScope = (role, viewerId, viewerDepartments) => {
  if (!role || role === 'super-admin') return {}

  if (role === 'auditor') {
    return {
      viewerScope: { $in: ['auditor', 'all'] },
    }
  }

  if (role === 'operator') {
    return {
      $or: [
        { operatorId: viewerId },
        { viewerScope: { $in: ['operator', viewerId].filter(Boolean) } },
      ],
    }
  }

  if (role === 'manager') {
    if (viewerDepartments?.length) {
      return {
        $or: [
          { viewerScope: { $in: ['manager', 'all'] } },
          { metadata: { department: { $in: viewerDepartments } } },
        ],
      }
    }

    return {
      viewerScope: { $in: ['manager', 'all'] },
    }
  }

  return {}
}

const buildFilters = (query) => {
  const filters = {}

  if (query.module && query.module !== 'all') filters.module = query.module
  if (query.operationType) filters.operationType = { $in: [].concat(query.operationType) }
  if (query.operatorKeyword) {
    filters.$or = [
      { operatorName: { $regex: query.operatorKeyword, $options: 'i' } },
      { operatorAccount: { $regex: query.operatorKeyword, $options: 'i' } },
    ]
  }
  if (query.operatorRole && query.operatorRole !== 'all') filters.operatorRole = query.operatorRole
  if (query.targetType && query.targetType !== 'all') filters.targetType = query.targetType
  if (query.targetKeyword) {
    filters.$or = filters.$or || []
    filters.$or.push({ targetName: { $regex: query.targetKeyword, $options: 'i' } })
    filters.$or.push({ targetId: { $regex: query.targetKeyword, $options: 'i' } })
  }
  if (query.resultStatus && query.resultStatus !== 'all') filters.resultStatus = query.resultStatus
  if (query.riskLevel && query.riskLevel !== 'all') filters.riskLevel = query.riskLevel
  if (query.isRead === 'false') filters.isRead = false
  if (query.isRead === 'true') filters.isRead = true
  if (query.isAbnormal === 'true') filters.isAbnormal = true
  if (query.chainId) filters.chainId = query.chainId
  if (query.correlationId) filters.correlationId = query.correlationId

  if (query.startTime || query.endTime) {
    filters.operationTime = {}
    if (query.startTime) filters.operationTime.$gte = new Date(query.startTime)
    if (query.endTime) filters.operationTime.$lte = new Date(query.endTime)
  }

  return filters
}

const combineFilters = (...args) => {
  const valid = args.filter((item) => item && Object.keys(item).length)
  if (!valid.length) return {}
  if (valid.length === 1) return valid[0]
  return { $and: valid }
}

const computeDiffFields = (beforeData, afterData) => {
  if (!beforeData || !afterData) return []

  const diff = new Set()
  const keys = new Set([...Object.keys(beforeData), ...Object.keys(afterData)])
  keys.forEach((key) => {
    if (JSON.stringify(beforeData[key]) !== JSON.stringify(afterData[key])) {
      diff.add(key)
    }
  })
  return Array.from(diff)
}

const evaluateRules = async (log) => {
  const rules = await AuditRuleModel.find({ enabled: true })
  const triggered = []

  for (const rule of rules) {
    if (rule.targetType && rule.targetType !== log.targetType) continue
    if (rule.operationTypes?.length && !rule.operationTypes.includes(log.operationType)) continue
    if (rule.fields?.length) {
      const hasIntersection = rule.fields.some((field) => log.diffFields?.includes(field))
      if (!hasIntersection) continue
    }

    if (rule.extraConditions?.resultStatus && rule.extraConditions.resultStatus !== log.resultStatus) continue

    if (rule.threshold && rule.threshold > 0) {
      const since = dayjs().subtract(rule.timeWindowMinutes || 10, 'minute').toDate()
      const count = await AuditLogModel.countDocuments({
        operatorId: log.operatorId,
        operationType: { $in: rule.operationTypes?.length ? rule.operationTypes : [log.operationType] },
        operationTime: { $gte: since },
      })

      if (count + 1 < rule.threshold) continue
    }

    triggered.push(rule)
  }

  return triggered
}

router.get('/', async (req, res) => {
  try {
    const page = Number(req.query.page || 1)
    const limit = Number(req.query.limit || 20)

    const roleFilter = applyRoleScope(req.query.viewerRole, req.query.viewerId, req.query.viewerDepartments?.split(','))
    const baseFilters = buildFilters(req.query)
    const filters = combineFilters(baseFilters, roleFilter)

    const query = AuditLogModel.find(filters)
      .sort({ operationTime: -1 })
      .skip((page - 1) * limit)
      .limit(Math.min(limit, 100))

    const [
      list,
      total,
      modules,
      operators,
      targetTypes,
      unreadCount,
      abnormalCount,
      unprocessedAbnormalCount,
      operationTypes,
    ] = await Promise.all([
      query.exec(),
      AuditLogModel.countDocuments(filters),
      AuditLogModel.distinct('module', filters),
      AuditLogModel.aggregate([
        { $match: filters },
        {
          $group: {
            _id: { operatorId: '$operatorId', operatorName: '$operatorName', operatorAccount: '$operatorAccount' },
            role: { $first: '$operatorRole' },
          },
        },
        { $limit: 100 },
      ]),
      AuditLogModel.distinct('targetType', filters),
      AuditLogModel.countDocuments(combineFilters(filters, { isRead: false })),
      AuditLogModel.countDocuments(combineFilters(filters, { isAbnormal: true })),
      AuditLogModel.countDocuments(combineFilters(filters, { isAbnormal: true, isRead: false })),
      AuditLogModel.distinct('operationType'),
    ])

    success(res, {
      list,
      total,
      page,
      limit,
      totalPages: Math.ceil(total / limit) || 1,
      aggregates: {
        modules: modules.filter(Boolean),
        operators: operators
          .map((item) => ({
            operatorId: item._id.operatorId,
            operatorName: item._id.operatorName,
            operatorAccount: item._id.operatorAccount,
            operatorRole: item.role,
          }))
          .filter((item) => item.operatorId || item.operatorAccount || item.operatorName),
        targetTypes: targetTypes.filter(Boolean),
        operationTypes,
        riskLevels: ['low', 'medium', 'high'],
      },
      counters: {
        unreadCount,
        abnormalCount,
        unprocessedAbnormalCount,
      },
    })
  } catch (error) {
    console.error('[auditLogs] 获取列表失败', error)
    failure(res, '获取審计日志失败')
  }
})

router.get('/stats', async (req, res) => {
  try {
    const now = dayjs()
    const startDate = now.subtract(Number(req.query.days || 14), 'day').startOf('day').toDate()
    const baseFilters = buildFilters(req.query)
    const roleFilter = applyRoleScope(req.query.viewerRole, req.query.viewerId, req.query.viewerDepartments?.split(','))
    const baseMatch = combineFilters(baseFilters, roleFilter)
    const timeScopedMatch = combineFilters(baseMatch, { operationTime: { $gte: startDate } })

    const [
      total,
      successCount,
      failureCount,
      highRiskCount,
      unreadCount,
      abnormalCount,
      unprocessedAbnormalCount,
      latestLog,
      dailyTrend,
      moduleStats,
      operationStats,
      heatmap,
      correlationChains,
    ] = await Promise.all([
      AuditLogModel.countDocuments(baseMatch),
      AuditLogModel.countDocuments(combineFilters(baseMatch, { resultStatus: 'success' })),
      AuditLogModel.countDocuments(combineFilters(baseMatch, { resultStatus: 'failure' })),
      AuditLogModel.countDocuments(combineFilters(baseMatch, { riskLevel: 'high' })),
      AuditLogModel.countDocuments(combineFilters(baseMatch, { isRead: false })),
      AuditLogModel.countDocuments(combineFilters(baseMatch, { isAbnormal: true })),
      AuditLogModel.countDocuments(combineFilters(baseMatch, { isAbnormal: true, isRead: false })),
      AuditLogModel.findOne(baseMatch).sort({ operationTime: -1 }).lean(),
      AuditLogModel.aggregate([
        { $match: timeScopedMatch },
        {
          $group: {
            _id: { $dateToString: { format: '%Y-%m-%d', date: '$operationTime' } },
            total: { $sum: 1 },
            success: { $sum: { $cond: [{ $eq: ['$resultStatus', 'success'] }, 1, 0] } },
            failure: { $sum: { $cond: [{ $eq: ['$resultStatus', 'failure'] }, 1, 0] } },
            highRisk: { $sum: { $cond: [{ $eq: ['$riskLevel', 'high'] }, 1, 0] } },
          },
        },
        { $sort: { _id: 1 } },
      ]),
      AuditLogModel.aggregate([
        { $match: baseMatch },
        {
          $group: {
            _id: '$module',
            count: { $sum: 1 },
            highRisk: { $sum: { $cond: [{ $eq: ['$riskLevel', 'high'] }, 1, 0] } },
          },
        },
        { $sort: { count: -1 } },
        { $limit: 20 },
      ]),
      AuditLogModel.aggregate([
        { $match: baseMatch },
        {
          $group: {
            _id: '$operationType',
            count: { $sum: 1 },
            failure: { $sum: { $cond: [{ $eq: ['$resultStatus', 'failure'] }, 1, 0] } },
          },
        },
        { $sort: { count: -1 } },
      ]),
      AuditLogModel.aggregate([
        { $match: timeScopedMatch },
        {
          $project: {
            dayOfWeek: { $dayOfWeek: '$operationTime' },
            hour: { $hour: '$operationTime' },
            riskLevel: 1,
          },
        },
        {
          $group: {
            _id: { dayOfWeek: '$dayOfWeek', hour: '$hour', riskLevel: '$riskLevel' },
            count: { $sum: 1 },
          },
        },
      ]),
      AuditLogModel.aggregate([
        { $match: combineFilters(baseMatch, { chainId: { $exists: true, $ne: null } }) },
        { $sort: { chainId: 1, operationTime: 1 } },
        {
          $group: {
            _id: '$chainId',
            logs: {
              $push: {
                operationTime: '$operationTime',
                operatorName: '$operatorName',
                operationType: '$operationType',
                targetName: '$targetName',
                resultStatus: '$resultStatus',
                riskLevel: '$riskLevel',
              },
            },
            targetType: { $first: '$targetType' },
            targetName: { $first: '$targetName' },
            module: { $first: '$module' },
          },
        },
        { $sort: { 'logs.0.operationTime': -1 } },
        { $limit: 10 },
      ]),
    ])

    success(res, {
      total,
      successCount,
      failureCount,
      highRiskCount,
      unreadCount,
      abnormalCount,
      unprocessedAbnormalCount,
      latestLog,
      dailyTrend: dailyTrend.map((item) => ({
        date: item._id,
        total: item.total,
        success: item.success,
        failure: item.failure,
        highRisk: item.highRisk,
      })),
      moduleStats: moduleStats.map((item) => ({
        module: item._id || '通用模块',
        count: item.count,
        highRisk: item.highRisk,
      })),
      operationStats: operationStats.map((item) => ({
        operationType: item._id || 'other',
        count: item.count,
        failure: item.failure,
      })),
      riskHeatmap: heatmap.map((item) => ({
        dayOfWeek: item._id.dayOfWeek,
        hour: item._id.hour,
        riskLevel: item._id.riskLevel,
        count: item.count,
      })),
      correlationChains: correlationChains.map((item) => ({
        chainId: item._id,
        targetType: item.targetType,
        targetName: item.targetName,
        module: item.module,
        logs: item.logs,
      })),
      successRate: total ? Number(((successCount / total) * 100).toFixed(2)) : 100,
    })
  } catch (error) {
    console.error('[auditLogs] 获取统计失败', error)
    failure(res, '获取审计日志统计失败')
  }
})

router.post('/', async (req, res) => {
  try {
    const payload = req.body || {}
    payload.operationTime = payload.operationTime ? new Date(payload.operationTime) : new Date()
    payload.diffFields = computeDiffFields(payload.beforeData, payload.afterData)

    if (!payload.operationType) {
      payload.operationType = 'other'
    }

    const triggeredRules = await evaluateRules(payload)

    if (triggeredRules.length) {
      payload.isAbnormal = true
      const highestRiskRule = triggeredRules.reduce((prev, current) =>
        riskPriority[current.riskLevel] > riskPriority[prev.riskLevel] ? current : prev
      )
      if (riskPriority[highestRiskRule.riskLevel] >= riskPriority[payload.riskLevel || 'low']) {
        payload.riskLevel = highestRiskRule.riskLevel
        payload.riskReason = highestRiskRule.description || `触发规则：${highestRiskRule.name}`
      }
      payload.alertChannels = triggeredRules
        .flatMap((rule) => rule.notifyChannels || [])
        .filter((value, index, array) => array.indexOf(value) === index)
    }

    const log = await AuditLogModel.create(payload)

    await Promise.all(
      triggeredRules.map(async (rule) => {
        rule.triggerCount += 1
        await rule.save()
        if (riskPriority[rule.riskLevel] >= riskPriority.high || rule.notifyChannels?.includes('email')) {
          await handleHighRiskAlert(log, rule)
        }
      })
    )

    if (!triggeredRules.length && riskPriority[payload.riskLevel || 'low'] >= riskPriority.high) {
      await handleHighRiskAlert(log)
    }

    success(res, log, '记录审计日志成功')
  } catch (error) {
    console.error('[auditLogs] 创建失败', error)
    failure(res, '记录審计日志失败')
  }
})

router.post('/batch/mark-read', async (req, res) => {
  try {
    const { ids = [], isRead = true, acknowledgedBy } = req.body
    if (!ids.length) return failure(res, '请选择日志记录', 400)

    await AuditLogModel.updateMany(
      { _id: { $in: ids } },
      {
        $set: {
          isRead,
          acknowledgedBy: isRead ? acknowledgedBy : null,
          acknowledgedAt: isRead ? new Date() : null,
        },
      }
    )

    success(res, null, '批量更新成功')
  } catch (error) {
    console.error('[auditLogs] 批量更新失败', error)
    failure(res, '批量更新日志状态失败')
  }
})

router.post('/export', async (req, res) => {
  try {
    const { format = 'excel', filters = {}, viewerRole, viewerId, viewerDepartments } = req.body || {}
    const roleFilter = applyRoleScope(viewerRole, viewerId, viewerDepartments)
    const queryFilters = combineFilters(buildFilters(filters), roleFilter)
    const logs = await AuditLogModel.find(queryFilters).sort({ operationTime: -1 }).limit(5000).lean()

    if (format === 'pdf') {
      const doc = new PDFDocument({ margin: 30, size: 'A4' })
      res.setHeader('Content-Type', 'application/pdf')
      res.setHeader('Content-Disposition', `attachment; filename=\"audit-logs-${Date.now()}.pdf\"`)
      doc.pipe(res)

      doc.fontSize(16).text('操作审计日志导出', { align: 'center' })
      doc.moveDown()

      logs.forEach((log, index) => {
        doc.fontSize(12).fillColor('#111').text(`${index + 1}. 模块：${log.module || '--'}`)
        doc.fontSize(11).fillColor('#333').text(`时间：${dayjs(log.operationTime).format('YYYY-MM-DD HH:mm:ss')}`)
        doc.text(`操作人：${log.operatorName || log.operatorAccount || '--'}（${log.operatorRole || '--'}）`)
        doc.text(`操作类型：${log.operationType}，对象：${log.targetType || '--'} ${log.targetName || ''}`)
        doc.text(`结果：${log.resultStatus}${log.resultMessage ? `（${log.resultMessage}）` : ''}`)
        doc.text(`风险等级：${log.riskLevel || '--'}，异常：${log.isAbnormal ? '是' : '否'}`)
        if (log.beforeData) {
          doc.text(`操作前：${JSON.stringify(log.beforeData)}`)
        }
        if (log.afterData) {
          doc.text(`操作后：${JSON.stringify(log.afterData)}`)
        }
        doc.moveDown()
      })
      doc.end()
      return
    }

    const workbook = new ExcelJS.Workbook()
    const worksheet = workbook.addWorksheet('审计日志')
    worksheet.columns = [
      { header: '时间', key: 'operationTime', width: 20 },
      { header: '模块', key: 'module', width: 20 },
      { header: '操作类型', key: 'operationType', width: 12 },
      { header: '操作人', key: 'operator', width: 18 },
      { header: '角色', key: 'operatorRole', width: 12 },
      { header: '目标对象', key: 'target', width: 24 },
      { header: '结果', key: 'result', width: 14 },
      { header: '风险等级', key: 'riskLevel', width: 10 },
      { header: '异常', key: 'isAbnormal', width: 8 },
      { header: 'IP地址', key: 'ipAddress', width: 16 },
      { header: '操作前内容', key: 'beforeData', width: 40 },
      { header: '操作后内容', key: 'afterData', width: 40 },
    ]

    logs.forEach((log) => {
      worksheet.addRow({
        operationTime: dayjs(log.operationTime).format('YYYY-MM-DD HH:mm:ss'),
        module: log.module || '--',
        operationType: log.operationType,
        operator: log.operatorName || log.operatorAccount || '--',
        operatorRole: log.operatorRole || '--',
        target: `${log.targetType || '--'} ${log.targetName || ''}`,
        result: `${log.resultStatus}${log.resultMessage ? `（${log.resultMessage}）` : ''}`,
        riskLevel: log.riskLevel || '--',
        isAbnormal: log.isAbnormal ? '是' : '否',
        ipAddress: log.ipAddress || '--',
        beforeData: log.beforeData ? JSON.stringify(log.beforeData) : '',
        afterData: log.afterData ? JSON.stringify(log.afterData) : '',
      })
    })

    res.setHeader(
      'Content-Type',
      'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
    )
    res.setHeader('Content-Disposition', `attachment; filename=\"audit-logs-${Date.now()}.xlsx\"`)
    await workbook.xlsx.write(res)
    res.end()
  } catch (error) {
    console.error('[auditLogs] 导出失败', error)
    failure(res, '导出审计日志失败')
  }
})

router.get('/rules', async (req, res) => {
  try {
    const rules = await AuditRuleModel.find().sort({ createdAt: -1 })
    success(res, rules)
  } catch (error) {
    console.error('[auditLogs] 获取规则失败', error)
    failure(res, '获取审计规则失败')
  }
})

router.post('/rules', async (req, res) => {
  try {
    const rule = await AuditRuleModel.create(req.body)
    success(res, rule, '创建审计规则成功')
  } catch (error) {
    console.error('[auditLogs] 创建规则失败', error)
    failure(res, '创建审计规则失败')
  }
})

router.put('/rules/:id', async (req, res) => {
  try {
    const { id } = req.params
    const rule = await AuditRuleModel.findByIdAndUpdate(id, req.body, { new: true })
    if (!rule) return failure(res, '审计规则不存在', 404)
    success(res, rule, '更新审计规则成功')
  } catch (error) {
    console.error('[auditLogs] 更新规则失败', error)
    failure(res, '更新审计规则失败')
  }
})

router.delete('/rules/:id', async (req, res) => {
  try {
    const { id } = req.params
    const rule = await AuditRuleModel.findByIdAndDelete(id)
    if (!rule) return failure(res, '审计规则不存在', 404)
    success(res, null, '删除审计规则成功')
  } catch (error) {
    console.error('[auditLogs] 删除规则失败', error)
    failure(res, '删除审计规则失败')
  }
})

module.exports = router

